﻿//CandleHelper.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Candles
{
	using System;
	using System.Collections.Generic;
	using System.Linq;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.ComponentModel;

	using StockSharp.Algo.Candles.Compression;
	using StockSharp.BusinessEntities;

	/// <summary>
	/// Вспомогательный класс для работы со свечками.
	/// </summary>
	public static class CandleHelper
	{
		/// <summary>
		/// Создать <see cref="CandleSeries"/> для свечек <see cref="TimeFrameCandle"/>.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="arg">Значение <see cref="TimeFrameCandle.TimeFrame"/>.</param>
		/// <returns>Серия свечек.</returns>
		public static CandleSeries TimeFrame(this Security security, TimeSpan arg)
		{
			return new CandleSeries(typeof(TimeFrameCandle), security, arg);
		}

		/// <summary>
		/// Создать <see cref="CandleSeries"/> для свечек <see cref="RangeCandle"/>.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="arg">Значение <see cref="RangeCandle.PriceRange"/>.</param>
		/// <returns>Серия свечек.</returns>
		public static CandleSeries Range(this Security security, Unit arg)
		{
			return new CandleSeries(typeof(RangeCandle), security, arg);
		}

		/// <summary>
		/// Создать <see cref="CandleSeries"/> для свечек <see cref="VolumeCandle"/>.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="arg">Значение <see cref="VolumeCandle.Volume"/>.</param>
		/// <returns>Серия свечек.</returns>
		public static CandleSeries Volume(this Security security, decimal arg)
		{
			return new CandleSeries(typeof(VolumeCandle), security, arg);
		}

		/// <summary>
		/// Создать <see cref="CandleSeries"/> для свечек <see cref="TickCandle"/>.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="arg">Значение <see cref="TickCandle.MaxTradeCount"/>.</param>
		/// <returns>Серия свечек.</returns>
		public static CandleSeries Tick(this Security security, decimal arg)
		{
			return new CandleSeries(typeof(TickCandle), security, arg);
		}

		/// <summary>
		/// Создать <see cref="CandleSeries"/> для свечек <see cref="PnFCandle"/>.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="arg">Значение <see cref="PnFCandle.PnFArg"/>.</param>
		/// <returns>Серия свечек.</returns>
		public static CandleSeries PnF(this Security security, PnFArg arg)
		{
			return new CandleSeries(typeof(PnFCandle), security, arg);
		}

		/// <summary>
		/// Создать <see cref="CandleSeries"/> для свечек <see cref="RenkoCandle"/>.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="arg">Значение <see cref="RenkoCandle.BoxSize"/>.</param>
		/// <returns>Серия свечек.</returns>
		public static CandleSeries Renko(this Security security, Unit arg)
		{
			return new CandleSeries(typeof(RenkoCandle), security, arg);
		}

		/// <summary>
		/// Запустить получение свечек.
		/// </summary>
		/// <param name="manager">Менеджер свечек.</param>
		/// <param name="series">Серия свечек.</param>
		public static void Start(this ICandleManager manager, CandleSeries series)
		{
			manager.ThrowIfNull().Start(series, series.From, series.To);
		}

		/// <summary>
		/// Прекратить получение свечек.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		public static void Stop(this CandleSeries series)
		{
			var manager = series.ThrowIfNull().CandleManager;

			// серию ранее не запускали, значит и останавливать не нужно
			if (manager == null)
				return;

			manager.Stop(series);
		}

		private static ICandleManagerContainer GetContainer(this CandleSeries series)
		{
			return series.ThrowIfNull().CandleManager.Container;
		}

		/// <summary>
		/// Получить количество свечек.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <returns>Количество свечек.</returns>
		public static int GetCandleCount(this CandleSeries series)
		{
			return series.GetContainer().GetCandleCount(series);
		}

		/// <summary>
		/// Получить все свечки на период <paramref name="time"/>.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <param name="time">Период свечки.</param>
		/// <returns>Свечки.</returns>
		public static IEnumerable<TCandle> GetCandles<TCandle>(this CandleSeries series, DateTime time) 
			where TCandle : Candle
		{
			return series.GetContainer().GetCandles(series, time).OfType<TCandle>();
		}

		/// <summary>
		/// Получить все свечки.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <returns>Свечки.</returns>
		public static IEnumerable<TCandle> GetCandles<TCandle>(this CandleSeries series)
			where TCandle : Candle
		{
			return series.GetContainer().GetCandles(series).OfType<TCandle>();
		}

		/// <summary>
		/// Получить свечки по диапазону дат.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <param name="timeRange">Диапазон дат, в которые должны входить свечки. Учитывается значение <see cref="Candle.OpenTime"/>.</param>
		/// <returns>Найденные свечки.</returns>
		public static IEnumerable<TCandle> GetCandles<TCandle>(this CandleSeries series, Range<DateTime> timeRange)
			where TCandle : Candle
		{
			return series.GetContainer().GetCandles(series, timeRange).OfType<TCandle>();
		}

		/// <summary>
		/// Получить свечки по общему количеству.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <param name="candleCount">Количество свечек, которое необходимо вернуть.</param>
		/// <returns>Найденные свечки.</returns>
		public static IEnumerable<TCandle> GetCandles<TCandle>(this CandleSeries series, int candleCount)
		{
			return series.GetContainer().GetCandles(series, candleCount).OfType<TCandle>();
		}

		/// <summary>
		/// Получить свечку по индексу.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <param name="candleIndex">Порядковый номер свечки с конца.</param>
		/// <returns>Найденая свечка. Если свечки не существует, то будет возвращено null.</returns>
		public static TCandle GetCandle<TCandle>(this CandleSeries series, int candleIndex)
			where TCandle : Candle
		{
			return (TCandle)series.GetContainer().GetCandle(series, candleIndex);
		}

		/// <summary>
		/// Получить временную свечку за определенную дату.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <param name="time">Дата свечки.</param>
		/// <returns>Найденная свечка (null, если свечка по заданным критериям не существует).</returns>
		public static TimeFrameCandle GetTimeFrameCandle(this CandleSeries series, DateTime time)
		{
			return series.GetCandles<TimeFrameCandle>().FirstOrDefault(c => c.OpenTime == time);
		}

		/// <summary>
		/// Получить текущую свечку.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <returns>Найденная свечка. Если свечка не существует, то будет возвращено null.</returns>
		public static TCandle GetCurrentCandle<TCandle>(this CandleSeries series)
			where TCandle : Candle
		{
			return series.GetCandle<TCandle>(0);
		}

		/// <summary>
		/// Получить серию свечек по заданным параметрам.
		/// </summary>
		/// <param name="manager">Менеджер свечек.</param>
		/// <param name="security">Инструмент, по которому нужно фильтровать сделки для формирования свечек.</param>
		/// <param name="arg">Параметр свечки.</param>
		/// <returns>Серия свечек. Null, если такая серия не зарегистрирована.</returns>
		public static CandleSeries GetSeries<TCandle>(this ICandleManager manager, Security security, object arg)
			where TCandle : Candle
		{
			return manager.ThrowIfNull().Series.FirstOrDefault(s => s.CandleType == typeof(TCandle) && s.Security == security && s.Arg.Equals(arg));
		}

		private static CandleSeries ThrowIfNull(this CandleSeries series)
		{
			if (series == null)
				throw new ArgumentNullException("series");

			return series;
		}

		private static ICandleManager ThrowIfNull(this ICandleManager manager)
		{
			if (manager == null)
				throw new ArgumentNullException("manager");

			return manager;
		}

		private sealed class CandleEnumerable<TValue> : SimpleEnumerable<Candle>, IEnumerableEx<Candle>
		{
			private sealed class CandleEnumerator : SimpleEnumerator<Candle>
			{
				private sealed class EnumeratorCandleBuilderSource : ConvertableCandleBuilderSource<TValue>
				{
					private readonly Security _security;

					public EnumeratorCandleBuilderSource(Security security)
					{
						if (security == null)
							throw new ArgumentNullException("security");

						_security = security;
					}

					public override int SpeedPriority
					{
						get { return 0; }
					}

					public override IEnumerable<Range<DateTime>> GetSupportedRanges(CandleSeries series)
					{
						if (series == null)
							throw new ArgumentNullException("series");

						if (series.Security != _security)
							yield break;

						yield return new Range<DateTime>(DateTime.MinValue, DateTime.MaxValue);
					}

					public override void Start(CandleSeries series, DateTime from, DateTime to)
					{
					}

					public override void Stop(CandleSeries series)
					{
						RaiseStopped(series);
					}

					public void PushNewValue(CandleSeries series, TValue value)
					{
						NewSourceValues(series, new[] { value });
					}
				}

				private readonly CandleSeries _series;
				private bool _isNewCandle;
				private readonly IEnumerator<TValue> _valuesEnumerator;
				private readonly EnumeratorCandleBuilderSource _builderSource;
				private Candle _lastCandle;
				private readonly CandleManager _candleManager;

				public CandleEnumerator(CandleSeries series, IEnumerable<TValue> values)
				{
					if (series == null)
						throw new ArgumentNullException("series");

					if (values == null)
						throw new ArgumentNullException("values");

					_series = series;
					_series.ProcessCandle += SeriesOnProcessCandle;

					_valuesEnumerator = values.GetEnumerator();

					_candleManager = new CandleManager();

					_builderSource = new EnumeratorCandleBuilderSource(series.Security);
					_candleManager.Sources.OfType<ICandleBuilder>().ForEach(b => b.Sources.Add(_builderSource));

					_candleManager.Start(series);
				}

				private void SeriesOnProcessCandle(Candle candle)
				{
					_lastCandle = candle;

					if (candle.State != CandleStates.Finished)
						return;

					Current = candle;
					_isNewCandle = true;
				}

				public override bool MoveNext()
				{
					while (!_isNewCandle)
					{
						if (!_valuesEnumerator.MoveNext())
							break;

						_builderSource.PushNewValue(_series, _valuesEnumerator.Current);
					}

					if (_isNewCandle)
					{
						_isNewCandle = false;
						return true;
					}

					if (_lastCandle != null)
					{
						Current = _lastCandle;
						_lastCandle = null;
						return true;
					}
					else
					{
						Current = null;
						return false;
					}
				}

				protected override void DisposeManaged()
				{
					Reset();
					_series.ProcessCandle -= SeriesOnProcessCandle;
					_series.Stop();
					_candleManager.Dispose();

					base.DisposeManaged();
				}
			}

			private readonly IEnumerableEx<TValue> _values;

			public CandleEnumerable(CandleSeries series, IEnumerableEx<TValue> values)
				: base(() => new CandleEnumerator(series, values))
			{
				_values = values;
			}

			int IEnumerableEx<Candle>.Count
			{
				get { return _values.Count; }
			}
		}

		/// <summary>
		/// Построить свечки из коллекции тиковых сделок.
		/// </summary>
		/// <typeparam name="TCandle">Тип свечек.</typeparam>
		/// <param name="trades">Тиковые сделки.</param>
		/// <param name="arg">Параметр свечки.</param>
		/// <returns>Свечки.</returns>
		public static IEnumerable<TCandle> ToCandles<TCandle>(this IEnumerableEx<Trade> trades, object arg)
			where TCandle : Candle
		{
			var firstTrade = trades.FirstOrDefault();

			if (firstTrade == null)
				return Enumerable.Empty<TCandle>();

			return trades.ToCandles<Trade, TCandle>(firstTrade.Security, arg);
		}

		/// <summary>
		/// Построить свечки из коллекции стаканов.
		/// </summary>
		/// <param name="depths">Стаканы.</param>
		/// <param name="series">Серия свечек.</param>
		/// <returns>Свечки.</returns>
		public static IEnumerableEx<Candle> ToCandles(this IEnumerableEx<MarketDepth> depths, CandleSeries series)
		{
			return depths.ToCandles<MarketDepth>(series);
		}

		/// <summary>
		/// Построить свечки из коллекции тиковых сделок.
		/// </summary>
		/// <param name="trades">Тиковые сделки.</param>
		/// <param name="series">Серия свечек.</param>
		/// <returns>Свечки.</returns>
		public static IEnumerableEx<Candle> ToCandles(this IEnumerableEx<Trade> trades, CandleSeries series)
		{
			return trades.ToCandles<Trade>(series);
		}

		/// <summary>
		/// Построить свечки из коллекции данных.
		/// </summary>
		/// <typeparam name="TValue">Тип исходных данных.</typeparam>
		/// <typeparam name="TCandle">Тип свечек.</typeparam>
		/// <param name="values">Исходные данные.</param>
		/// <param name="security">Инструмент.</param>
		/// <param name="arg">Параметр свечки.</param>
		/// <returns>Свечки.</returns>
		public static IEnumerable<TCandle> ToCandles<TValue, TCandle>(this IEnumerableEx<TValue> values, Security security, object arg)
			where TCandle : Candle
		{
			return values.ToCandles(typeof(TCandle), security, arg).Cast<TCandle>();
		}

		/// <summary>
		/// Построить свечки из коллекции данных.
		/// </summary>
		/// <typeparam name="TValue">Тип исходных данных.</typeparam>
		/// <param name="values">Исходные данные.</param>
		/// <param name="candleType">Тип свечек.</param>
		/// <param name="security">Инструмент.</param>
		/// <param name="arg">Параметр свечки.</param>
		/// <returns>Свечки.</returns>
		public static IEnumerableEx<Candle> ToCandles<TValue>(this IEnumerableEx<TValue> values, Type candleType, Security security, object arg)
		{
			return values.ToCandles(new CandleSeries(candleType, security, arg));
		}

		/// <summary>
		/// Построить свечки из коллекции данных.
		/// </summary>
		/// <typeparam name="TValue">Тип исходных данных.</typeparam>
		/// <param name="values">Исходные данные.</param>
		/// <param name="series">Серия свечек.</param>
		/// <returns>Свечки.</returns>
		public static IEnumerableEx<Candle> ToCandles<TValue>(this IEnumerableEx<TValue> values, CandleSeries series)
		{
			return new CandleEnumerable<TValue>(series, values);
		}

		/// <summary>
		/// Зарегистрирована ли группировка свечек по определённому признаку.
		/// </summary>
		/// <param name="manager">Менеджер свечек.</param>
		/// <param name="security">Инструмент, для которого зарегистрирована группировка.</param>
		/// <param name="arg">Параметр свечки.</param>
		/// <returns>True, если зарегистрирована. Иначе, false.</returns>
		public static bool IsCandlesRegistered<TCandle>(this ICandleManager manager, Security security, object arg)
			where TCandle : Candle
		{
			return manager.GetSeries<TCandle>(security, arg) != null;
		}

		/// <summary>
		/// Получить биржевое время.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <returns>Биржевое время.</returns>
		public static DateTime GetMarketTime(this CandleSeries series)
		{
			if (series == null)
				throw new ArgumentNullException("series");

			return series.Security.GetMarketTime();
		}

		/// <summary>
		/// Получить время формирования свечки.
		/// </summary>
		/// <param name="timeFrame">Тайм-фрейм, по которому необходимо получить время формирования свечки.</param>
		/// <param name="security">Инструмент, содержащий информацию о временной зоне работы биржи <see cref="Security.ExchangeBoard"/>.</param>
		/// <returns>Время формирования свечки.</returns>
		public static DateTime GetCandleTime(this TimeSpan timeFrame, Security security)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			return timeFrame.GetCandleTime(security.GetMarketTime());
		}

		/// <summary>
		/// Получить время формирования свечки.
		/// </summary>
		/// <param name="timeFrame">Тайм-фрейм, по которому необходимо получить время формирования свечки.</param>
		/// <param name="currentTime">Текущее время, входящее в диапазон временных рамок.</param>
		/// <returns>Время формирования свечки.</returns>
		public static DateTime GetCandleTime(this TimeSpan timeFrame, DateTime currentTime)
		{
			return timeFrame.GetCandleBounds(currentTime).Min;
		}

		/// <summary>
		/// Получить временные рамки свечки.
		/// </summary>
		/// <param name="timeFrame">Тайм-фрейм, по которому необходимо получить временные рамки.</param>
		/// <param name="security">Инструмент, содержащий информацию о временной зоне работы биржи <see cref="Security.ExchangeBoard"/>.</param>
		/// <returns>Временные рамки свечки.</returns>
		public static Range<DateTime> GetCandleBounds(this TimeSpan timeFrame, Security security)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			return timeFrame.GetCandleBounds(security.GetMarketTime(), security.ExchangeBoard);
		}

		/// <summary>
		/// Получить временные рамки свечки.
		/// </summary>
		/// <param name="timeFrame">Тайм-фрейм, по которому необходимо получить временные рамки.</param>
		/// <param name="currentTime">Текущее время, входящее в диапазон временных рамок.</param>
		/// <returns>Временные рамки свечки.</returns>
		public static Range<DateTime> GetCandleBounds(this TimeSpan timeFrame, DateTime currentTime)
		{
			return timeFrame.GetCandleBounds(currentTime, ExchangeBoard.Test);
		}

		/// <summary>
		/// Получить временные рамки свечки относительно времени работы биржи.
		/// </summary>
		/// <param name="timeFrame">Тайм-фрейм, по которому необходимо получить временные рамки.</param>
		/// <param name="currentTime">Текущее время, входящее в диапазон временных рамок.</param>
		/// <param name="board">Информация о площадке, из которой будет взято время работы <see cref="ExchangeBoard.WorkingTime"/>.</param>
		/// <returns>Временные рамки свечки.</returns>
		public static Range<DateTime> GetCandleBounds(this TimeSpan timeFrame, DateTime currentTime, ExchangeBoard board)
		{
			if (board == null)
				throw new ArgumentNullException("board");

			return timeFrame.GetCandleBounds(currentTime, board.WorkingTime);
		}

		/// <summary>
		/// Получить временные рамки свечки относительно режиме работы биржи.
		/// </summary>
		/// <param name="timeFrame">Тайм-фрейм, по которому необходимо получить временные рамки.</param>
		/// <param name="currentTime">Текущее время, входящее в диапазон временных рамок.</param>
		/// <param name="time">Информация о режиме работы биржи.</param>
		/// <returns>Временные рамки свечки.</returns>
		public static Range<DateTime> GetCandleBounds(this TimeSpan timeFrame, DateTime currentTime, WorkingTime time)
		{
			if (time == null)
				throw new ArgumentNullException("time");

			TimeSpan startFrom;

			if (time.Times.Length > 0)
			{
				//var range = time.Times.FirstOrDefault(r => r.Contains(currentTime.TimeOfDay));

				//if (range == null)
				//	throw new ArgumentOutOfRangeException("currentTime", currentTime, "Текущее время currentTime не входит в диапазон работы биржи.");

				//startFrom = range.Min;

				// http://stocksharp.com/forum/yaf_postsm13887_RealtimeEmulationTrader---niepravil-nyie-sviechi.aspx#post13887
				// отсчет свечек идет от начала сессии и игнорируются клиринги
				startFrom = time.Times[0].Min;
			}
			else
				startFrom = TimeSpan.Zero;

			var length = (currentTime.TimeOfDay - startFrom).To<long>();

			var beginTime = currentTime.Date + (startFrom + length.Floor(timeFrame.Ticks).To<TimeSpan>());
			var endTime = beginTime + timeFrame;

			return new Range<DateTime>(
				new DateTime(beginTime.Year, beginTime.Month, beginTime.Day, beginTime.Hour, beginTime.Minute, beginTime.Second, beginTime.Millisecond),
				new DateTime(endTime.Year, endTime.Month, endTime.Day, endTime.Hour, endTime.Minute, endTime.Second, endTime.Millisecond));
		}

		/// <summary>
		/// Получить длину свечки.
		/// </summary>
		/// <param name="candle">Свечка, для которой необходимо получить длины.</param>
		/// <returns>Длина свечки.</returns>
		public static decimal GetLength(this Candle candle)
		{
			if (candle == null)
				throw new ArgumentNullException("candle");

			return candle.HighPrice - candle.LowPrice;
		}

		/// <summary>
		/// Получить тело свечки.
		/// </summary>
		/// <param name="candle">Свечка, для которой необходимо получить тело.</param>
		/// <returns>Тело свечки.</returns>
		public static decimal GetBody(this Candle candle)
		{
			if (candle == null)
				throw new ArgumentNullException("candle");

			return (candle.OpenPrice - candle.ClosePrice).Abs();
		}

		/// <summary>
		/// Получить длину верхней тени свечки.
		/// </summary>
		/// <param name="candle">Свечка, для которой необходимо получить длины верхней тени.</param>
		/// <returns>Длина верхней тени свечки. Если 0, то тень отсутствует.</returns>
		public static decimal GetTopShadow(this Candle candle)
		{
			if (candle == null)
				throw new ArgumentNullException("candle");

			return candle.HighPrice - candle.OpenPrice.Max(candle.ClosePrice);
		}

		/// <summary>
		/// Получить длину нижней тени свечки.
		/// </summary>
		/// <param name="candle">Свечка, для которой необходимо получить длины нижней тени.</param>
		/// <returns>Длина нижней тени свечки. Если 0, то тень отсутствует.</returns>
		public static decimal GetBottomShadow(this Candle candle)
		{
			if (candle == null)
				throw new ArgumentNullException("candle");

			return candle.OpenPrice.Min(candle.ClosePrice) - candle.LowPrice;
		}

		//
		// http://en.wikipedia.org/wiki/Candlestick_chart
		//

		/// <summary>
		/// Белая ли или черная свечка.
		/// </summary>
		/// <param name="candle">Свечка, для которой необходимо определить цвет.</param>
		/// <returns>True, если свечка белая, false, если черная, и null, если свечка плоская.</returns>
		public static bool? IsWhiteOrBlack(this Candle candle)
		{
			if (candle == null)
				throw new ArgumentNullException("candle");

			if (candle.OpenPrice == candle.ClosePrice)
				return null;

			return candle.OpenPrice < candle.ClosePrice;
		}

		/// <summary>
		/// Бестеневая ли свечка тени.
		/// </summary>
		/// <param name="candle">Свечка, для которой необходимо определить наличие теней.</param>
		/// <returns>True, если свечка не имеет теней, false, если имеет.</returns>
		public static bool IsMarubozu(this Candle candle)
		{
			if (candle == null)
				throw new ArgumentNullException("candle");

			return candle.GetLength() == candle.GetBody();
		}

		/// <summary>
		/// Нейтральная ли свечка сделкам.
		/// </summary>
		/// <remarks>
		/// Нейтральность определяется как ситуация, когда в период свечки ни покупатели ни продавцы не создали тренд.
		/// </remarks>
		/// <param name="candle">Свечка, для которой необходимо рассчитать, нейтральна ли она.</param>
		/// <returns>True, если свечка нейтральна, false, если не нейтральная.</returns>
		public static bool IsSpinningTop(this Candle candle)
		{
			return !candle.IsMarubozu() && (candle.GetBottomShadow() == candle.GetTopShadow());
		}

		/// <summary>
		/// Является ли свечка молотом.
		/// </summary>
		/// <param name="candle">Свечка, которую необходимо проверить на паттерн.</param>
		/// <returns>True, если является, false, если нет.</returns>
		public static bool IsHammer(this Candle candle)
		{
			return !candle.IsMarubozu() && (candle.GetBottomShadow() == 0 || candle.GetTopShadow() == 0);
		}

		/// <summary>
		/// Является ли свечка стрекозой или надгробьем.
		/// </summary>
		/// <param name="candle">Свечка, которую необходимо проверить на паттерн.</param>
		/// <returns>True, если стрекоза, false, если надгробье, null - ни то, ни другое.</returns>
		public static bool? IsDragonflyOrGravestone(this Candle candle)
		{
			if (candle.IsWhiteOrBlack() == null)
			{
				if (candle.GetTopShadow() == 0)
					return true;
				else if (candle.GetBottomShadow() == 0)
					return false;
			}

			return null;
		}

		/// <summary>
		/// Бычья ли или медвежья свечка.
		/// </summary>
		/// <param name="candle">Свечка, которую необходимо проверить на тренд.</param>
		/// <returns>True, если бычья, false, если медвежья, null - ни то, ни другое.</returns>
		public static bool? IsBullishOrBearish(this Candle candle)
		{
			if (candle == null)
				throw new ArgumentNullException("candle");

			var isWhiteOrBlack = candle.IsWhiteOrBlack();

			switch (isWhiteOrBlack)
			{
				case true:
					if (candle.GetBottomShadow() >= candle.GetBody())
						return true;
					break;
				case false:
					if (candle.GetTopShadow() >= candle.GetBody())
						return true;
					break;
			}

			return null;
		}

		/// <summary>
		/// Получить количество временных интервалов в пределах заданного отрезка времени.
		/// </summary>
		/// <param name="security">Инструмент, по которому вычисляется время работы биржи через свойство <see cref="Security.ExchangeBoard"/>.</param>
		/// <param name="range">Заданный отрезок времени, для которого нужно получить количество временных интервалов.</param>
		/// <param name="timeFrame">Размер временного интервала.</param>
		/// <returns>Полученное количество временных интервалов.</returns>
		public static long GetTimeFrameCount(this Security security, Range<DateTime> range, TimeSpan timeFrame)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			return security.ExchangeBoard.GetTimeFrameCount(range, timeFrame);
		}

		/// <summary>
		/// Получить количество временных интервалов в пределах заданного отрезка времени.
		/// </summary>
		/// <param name="board">Информация о площадке, по которому вычисляется время работы через свойство <see cref="ExchangeBoard.WorkingTime"/>.</param>
		/// <param name="range">Заданный отрезок времени, для которого нужно получить количество временных интервалов.</param>
		/// <param name="timeFrame">Размер временного интервала.</param>
		/// <returns>Полученное количество временных интервалов.</returns>
		public static long GetTimeFrameCount(this ExchangeBoard board, Range<DateTime> range, TimeSpan timeFrame)
		{
			if (board == null)
				throw new ArgumentNullException("board");

			return board.WorkingTime.GetTimeFrameCount(range, timeFrame);
		}

		/// <summary>
		/// Получить количество временных интервалов в пределах заданного отрезка времени.
		/// </summary>
		/// <param name="workingTime">Режим работы биржи.</param>
		/// <param name="range">Заданный отрезок времени, для которого нужно получить количество временных интервалов.</param>
		/// <param name="timeFrame">Размер временного интервала.</param>
		/// <returns>Полученное количество временных интервалов.</returns>
		public static long GetTimeFrameCount(this WorkingTime workingTime, Range<DateTime> range, TimeSpan timeFrame)
		{
			if (workingTime == null)
				throw new ArgumentNullException("workingTime");

			if (range == null)
				throw new ArgumentNullException("range");

			var to = range.Max;
			var from = range.Min;

			var days = (int)(to.Date - from.Date).TotalDays;

			if (workingTime.Times.IsEmpty())
			{
				return (to - from).Ticks / timeFrame.Ticks;
			}
			else
			{
				if (days == 0)
				{
					return workingTime.GetTimeFrameCount(new Range<TimeSpan>(from.TimeOfDay, to.TimeOfDay), timeFrame);
				}
				else
				{

					var totalCount = workingTime.GetTimeFrameCount(new Range<TimeSpan>(from.TimeOfDay, TimeSpanHelper.LessOneDay), timeFrame);
					totalCount += workingTime.GetTimeFrameCount(new Range<TimeSpan>(TimeSpan.Zero, to.TimeOfDay), timeFrame);

					if (days > 1)
					{
						var fullDayLength = workingTime.Times.Sum(r => r.Length.Ticks);
						totalCount += TimeSpan.FromTicks((days - 1) * fullDayLength).Ticks / timeFrame.Ticks;
					}

					return totalCount;
				}
			}
		}

		private static long GetTimeFrameCount(this WorkingTime workingTime, Range<TimeSpan> fromToRange, TimeSpan timeFrame)
		{
			if (workingTime == null)
				throw new ArgumentNullException("workingTime");

			if (fromToRange == null)
				throw new ArgumentNullException("fromToRange");

			return workingTime.Times
						.Select(fromToRange.Intersect)
						.Where(intersection => intersection != null)
						.Sum(intersection => intersection.Length.Ticks / timeFrame.Ticks);
		}

		internal static CandleSeries CheckSeries(this Candle candle)
		{
			if (candle == null)
				throw new ArgumentNullException("candle");

			var series = candle.Series;

			if (series == null)
				throw new ArgumentException("candle");

			return series;
		}

		internal static bool CheckTime(this CandleSeries series, DateTime time)
		{
			if (series == null)
				throw new ArgumentNullException("series");

			return series.WorkingTime.IsTradeTime(time) && time >= series.From && time < series.To;
		}
	}
}